<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Element UI Tree节点操作</title>
  <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
  <script src="https://unpkg.com/element-ui/lib/index.js"></script>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
      min-height: 100vh;
      display: flex;
      justify-content: center;
      align-items: center;
      padding: 20px;
      font-family: 'Helvetica Neue', Arial, sans-serif;
    }

    .container {
      width: 1200px;
      background: white;
      border-radius: 12px;
      box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
      overflow: hidden;
    }

    .header {
      background: linear-gradient(90deg, #409EFF 0%, #64b5f6 100%);
      color: white;
      padding: 20px 30px;
      text-align: center;
      position: relative;
    }

    .header h1 {
      font-size: 28px;
      margin-bottom: 8px;
      letter-spacing: 1px;
    }

    .header p {
      font-size: 16px;
      opacity: 0.9;
      max-width: 600px;
      margin: 0 auto;
    }

    .content {
      display: flex;
      padding: 25px;
      min-height: 500px;
    }

    .tree-container {
      flex: 1;
      border-right: 1px solid #eee;
      padding-right: 25px;
    }

    .operation-container {
      width: 300px;
      padding-left: 25px;
    }

    .operation-title {
      font-size: 18px;
      color: #409EFF;
      margin-bottom: 20px;
      padding-bottom: 10px;
      border-bottom: 2px solid #e0e0e0;
    }

    .operation-buttons {
      display: flex;
      flex-direction: column;
      gap: 12px;
    }

    .operation-buttons .el-button {
      width: 100%;
      height: 42px;
      font-size: 15px;
    }

    .context-menu {
      position: fixed;
      background: white;
      border-radius: 4px;
      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.2);
      z-index: 2000;
      padding: 8px 0;
      min-width: 120px;
    }

    .context-menu-item {
      padding: 8px 20px;
      font-size: 14px;
      cursor: pointer;
      color: #606266;
      transition: all 0.3s;
    }

    .context-menu-item:hover {
      background: #ecf5ff;
      color: #409EFF;
    }

    .custom-tree-node {
      flex: 1;
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 10px;
      font-size: 15px;
      padding: 0 4px;
    }

    .el-tree-node__content {
      height: 36px !important;
    }

    .tree-node-label {
      transition: all 0.3s;
      padding: 3px 8px;
      border-radius: 3px;
    }

    .tree-node-label:hover {
      background: #f0f7ff;
    }

    .tree-node-actions {
      display: none;
      transition: all 0.3s;
    }

    .el-tree-node__content:hover .tree-node-actions {
      display: flex;
      gap: 6px;
    }

    .action-btn {
      font-size: 14px;
      color: #909399;
      cursor: pointer;
      padding: 2px 5px;
      border-radius: 3px;
    }

    .action-btn:hover {
      background: #ecf5ff;
      color: #409EFF;
    }

    .instructions {
      background: #f8f9fa;
      border-left: 4px solid #409EFF;
      padding: 15px;
      border-radius: 0 4px 4px 0;
      margin-top: 25px;
      font-size: 14px;
    }

    .instructions h3 {
      color: #409EFF;
      margin-bottom: 10px;
    }

    .instructions ul {
      padding-left: 20px;
    }

    .instructions li {
      margin-bottom: 8px;
      line-height: 1.5;
    }

    .footer {
      text-align: center;
      padding: 20px;
      color: #888;
      font-size: 14px;
      border-top: 1px solid #eee;
    }

    .edit-input {
      width: 180px;
    }

    .empty-tree {
      text-align: center;
      color: #909399;
      padding: 40px 0;
      font-size: 15px;
    }

    .node-icon {
      margin-right: 6px;
      font-size: 16px;
      color: #ffb300;
    }

    .folder-icon {
      color: #ffb300;
    }

    .file-icon {
      color: #409EFF;
    }
  </style>
</head>

<body>
  <div id="app">
    <div class="container">
      <div class="header">
        <h1><i class="el-icon-menu"></i> Element UI Tree节点操作</h1>
        <p>支持节点的添加、编辑、删除等操作，提供右键菜单和按钮操作两种方式</p>
      </div>

      <div class="content">
        <div class="tree-container">
          <el-tree ref="tree" :data="treeData" node-key="id" default-expand-all :expand-on-click-node="false"
            @node-click="handleNodeClick" @node-contextmenu="handleContextMenu">
            <div class="custom-tree-node" slot-scope="{ node, data }">
              <div v-if="data.isEdit">
                <el-input v-focus v-model="data.label" size="small" class="edit-input" @blur="confirmEdit(data)"
                  @keyup.enter.native="confirmEdit(data)" autofocus></el-input>
              </div>
              <div v-else>
                <span class="tree-node-label" @dblclick="editNode(data)">
                  <i :class="data.children ? 'el-icon-folder folder-icon' : 'el-icon-document file-icon'"></i>
                  {{ node.label }}
                </span>
              </div>
              <div class="tree-node-actions">
                <span class="action-btn" @click.stop="addSiblingNode(data)" title="添加同级节点">
                  <i class="el-icon-plus"></i>
                </span>
                <span class="action-btn" @click.stop="addChildNode(data)" title="添加子节点">
                  <i class="el-icon-folder-add"></i>
                </span>
                <span class="action-btn" @click.stop="editNode(data)" title="编辑节点">
                  <i class="el-icon-edit"></i>
                </span>
                <span class="action-btn" @click.stop="deleteNode(data)" title="删除节点">
                  <i class="el-icon-delete"></i>
                </span>
              </div>
            </div>
          </el-tree>

          <div v-if="treeData.length === 0" class="empty-tree">
            <i class="el-icon-info" style="font-size: 24px; margin-bottom: 10px;"></i>
            <p>树结构为空，请添加根节点</p>
          </div>
        </div>

        <div class="operation-container">
          <h3 class="operation-title">节点操作</h3>
          <div class="operation-buttons">
            <el-button type="primary" icon="el-icon-circle-plus-outline" @click="addRootNode">添加根节点</el-button>
            <el-button type="success" icon="el-icon-plus" :disabled="!currentNode"
              @click="addChildNode(currentNode)">添加子节点</el-button>
            <el-button type="warning" icon="el-icon-edit" :disabled="!currentNode"
              @click="editNode(currentNode)">编辑节点</el-button>
            <el-button type="danger" icon="el-icon-delete" :disabled="!currentNode"
              @click="deleteNode(currentNode)">删除节点</el-button>
          </div>

          <div class="instructions">
            <h3>操作指南</h3>
            <ul>
              <li>右键点击节点可打开操作菜单</li>
              <li>双击节点可进行编辑</li>
              <li>鼠标悬停在节点上会显示操作按钮</li>
              <li>使用右侧按钮可进行节点操作</li>
              <li>添加节点后，按Enter键或点击其他地方保存</li>
              <li>删除节点需要二次确认</li>
            </ul>
          </div>
        </div>
      </div>

      <div v-show="contextMenuVisible" class="context-menu"
        :style="{left: contextMenuLeft + 'px', top: contextMenuTop + 'px'}">
        <div class="context-menu-item" @click="addSiblingNode(contextMenuData)"><i class="el-icon-plus"></i> 添加同级节点
        </div>
        <div class="context-menu-item" @click="addChildNode(contextMenuData)"><i class="el-icon-folder-add"></i> 添加子节点
        </div>
        <div class="context-menu-item" @click="editNode(contextMenuData)"><i class="el-icon-edit"></i> 编辑节点</div>
        <div class="context-menu-item" @click="deleteNode(contextMenuData)"><i class="el-icon-delete"></i> 删除节点</div>
      </div>

      <div class="footer">
        Element UI Tree 节点操作示例 | 使用Vue.js和Element UI实现
      </div>
    </div>
  </div>

  <script>
    new Vue({
      el: '#app',
      directives: {
        // 注册一个局部的自定义指令 v-focus
        focus: {
          // 指令的定义
          inserted: function (el) {
            // 聚焦元素
            el.querySelector('input').focus()
          }
        }
      },
      data() {
        return {
          treeData: [
            {
              id: 1,
              label: '根节点 1',
              children: [
                { id: 4, label: '子节点 1-1' },
                { id: 5, label: '子节点 1-2' }
              ]
            },
            {
              id: 2,
              label: '根节点 2',
              children: [
                { id: 6, label: '子节点 2-1' },
                { id: 7, label: '子节点 2-2' }
              ]
            },
            {
              id: 3,
              label: '根节点 3',
              children: [
                { id: 8, label: '子节点 3-1' },
                { id: 9, label: '子节点 3-2' }
              ]
            }
          ],
          currentNode: null,
          contextMenuVisible: false,
          contextMenuLeft: 0,
          contextMenuTop: 0,
          contextMenuData: null
        };
      },
      mounted() {
        // 点击其他地方关闭右键菜单
        document.addEventListener('click', () => {
          this.contextMenuVisible = false;
        });
      },
      methods: {
        // 生成唯一ID
        generateId() {
          return Date.now().toString(36) + Math.random().toString(36).substr(2);
        },
        // 处理节点点击
        handleNodeClick(data) {
          this.currentNode = data;
          this.contextMenuVisible = false;
        },
        // 处理右键菜单
        handleContextMenu(event, data, node, treeNode) {
          event.preventDefault();
          this.contextMenuVisible = true;
          this.contextMenuLeft = event.clientX;
          this.contextMenuTop = event.clientY;
          this.contextMenuData = data;
          this.currentNode = data;
        },
        // 添加根节点
        addRootNode() {
          const newNode = {
            // id: this.generateId(),
            id: undefined,
            label: '新根节点',
            isEdit: true,
            children: []
          };
          this.treeData.push(newNode);
          this.currentNode = newNode;
        },
        // 添加子节点
        addChildNode(data) {
          if (!data.children) {
            this.$set(data, 'children', []);
          }

          const newNode = {
            // id: this.generateId(),
            id: undefined,
            label: '新子节点',
            isEdit: true
          };

          data.children.push(newNode);
          this.contextMenuVisible = false;
          this.currentNode = newNode;
        },
        // 添加同级节点
        addSiblingNode(data) {
          const node = this.$refs.tree.getNode(data);
          const parent = node.parent;
          const level = node.level;

          if (level === 1) {
            // 根节点层级
            const newNode = {
              // id: this.generateId(),
              id: undefined,
              label: '新根节点',
              isEdit: true,
              children: []
            };
            this.treeData.push(newNode);
          } else {
            // 非根节点层级
            const newNode = {
              // id: this.generateId(),
              id: undefined,
              label: '新同级节点',
              isEdit: true
            };
            parent.data.children.push(newNode);
          }

          this.contextMenuVisible = false;
        },
        // 编辑节点
        editNode(data) {
          this.$set(data, 'isEdit', true);
          this.contextMenuVisible = false;
          this.currentNode = data;
        },
        // 确认编辑
        confirmEdit(data) {
          this.$set(data, 'isEdit', false);
          if (!data.label.trim()) {
            data.label = '未命名节点';
          }
          if (data.id) {
            console.log('编辑已有节点');
          } else {
            console.log('新增节点');
          }
        },
        // 删除节点
        deleteNode(data) {
          this.$confirm('确定要删除该节点及其所有子节点吗?', '提示', {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: 'warning'
          }).then(() => {
            const node = this.$refs.tree.getNode(data);
            const parent = node.parent;
            const children = parent.data.children || parent.data;
            const index = children.findIndex(d => d.id === data.id);
            children.splice(index, 1);
            this.$message.success('节点删除成功');
            this.contextMenuVisible = false;
          }).catch(() => { });
        }
      },
    });
  </script>
</body>

</html>